package com.kuizii.base.common.utils;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Clob;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Supplier;

/**
 * @author renxiaodong
 */
public class BeanHelper {
    static Logger log = LoggerFactory.getLogger(BeanHelper.class);

    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYY_MM_DD = "yyyy-MM-dd";

    static {
        rules = new HashMap<>();
        registerConverter(Date.class, new DefaultDateConverter());
        registerConverter(LocalDate.class, new DefaultLocalDateConverter());
        registerConverter(LocalDateTime.class, new DefaultLocalDateTimeConverter());
        registerConverter(Clob.class, new DefaultClobConverter());
        registerConverter(String.class, new DefaultStringConverter());
    }

    static Map<Class, Converter> rules;

    public static void registerConverter(Class cls, Converter converter) {
        rules.put(cls, converter);
    }

    public static void deregisterConverter(Class cls, Converter converter) {
        rules.remove(cls, converter);
    }

    public static Converter getConverter(Class cls) {
        Converter converter = rules.get(cls);
        if (converter == null) {
            for (Class keyClz : rules.keySet()) {
                if (keyClz.isAssignableFrom(cls)) {
                    converter = rules.get(keyClz);
                    rules.put(cls, converter);
                    break;
                }
            }
        }
        return converter;
    }

    public static class DefaultStringConverter implements Converter {

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
        SimpleDateFormat sdt = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS);
        SimpleDateFormat sd = new SimpleDateFormat(YYYY_MM_DD);

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }
            try {
                if (targetClass == String.class) {
                    return (T) sourceValue;
                } else if (targetClass == Date.class) {
                    int length = sourceValue.toString().length();
                    if (length >= YYYY_MM_DD_HH_MM_SS.length()) {
                        return (T) sdt.parse((String) sourceValue.toString().substring(0, YYYY_MM_DD_HH_MM_SS.length()));
                    } else if (length >= YYYY_MM_DD.length() && length <= YYYY_MM_DD_HH_MM_SS.length()) {
                        return (T) sd.parse((String) sourceValue.toString().substring(0, YYYY_MM_DD.length()));
                    }

                } else if (targetClass == LocalDate.class) {
                    return (T) LocalDate.parse((String) sourceValue, dateTimeFormatter);
                } else if (targetClass == LocalDateTime.class) {
                    return (T) LocalDateTime.parse((String) sourceValue, dateTimeFormatter);
                } else {
                    return ConverterSupport.cast(sourceValue, targetClass);
                }
            } catch (Exception ex) {
                log.warn("转换对象失败", ex);
                ex.printStackTrace();
            }
            return null;
        }
    }

    public static class DefaultDateConverter implements Converter {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }

            if (targetClass == Date.class) {
                return (T) sourceValue;
            } else if (targetClass == LocalDate.class) {
                Date date = (Date) sourceValue;
                LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault())    //设置当前系统时区
                        .toLocalDate();
                return (T) localDate;
            } else if (targetClass == LocalDateTime.class) {
                Date d = (Date) sourceValue;
                return (T) LocalDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault());
            } else if (targetClass == String.class) {
                return (T) dateTimeFormatter.format(((Date) sourceValue).toInstant());
            }
            return null;
        }
    }

    public static class DefaultLocalDateConverter implements Converter {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }
            LocalDate localDate = (LocalDate) sourceValue;
            if (targetClass == Date.class) {
              return   (T)Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
            } else if (targetClass == LocalDate.class) {
                return (T) sourceValue;
            } else if (targetClass == LocalDateTime.class) {
                return (T) LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(), 0, 0);
            } else if (targetClass == String.class) {
                return (T) localDate.format(dateTimeFormatter);
            }
            return null;
        }
    }

    public static class DefaultLocalDateTimeConverter implements Converter {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }
            LocalDateTime localDate = (LocalDateTime) sourceValue;
            if (targetClass == Date.class) {
                return (T) Date.from(localDate.atZone(ZoneId.systemDefault()).toInstant());
            } else if (targetClass == LocalDate.class) {
                return (T) LocalDate.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth());
            } else if (targetClass == LocalDateTime.class) {
                return (T) sourceValue;
            } else if (targetClass == String.class) {
                return (T) localDate.format(dateTimeFormatter);
            }
            return null;
        }
    }

    public static class DefaultClobConverter implements Converter {

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null || !(sourceValue instanceof Clob)) {
                return null;
            }
            try {
                Clob clob = (Clob) sourceValue;
                if (targetClass == String.class) {

                    Reader reader = clob.getCharacterStream();
                    StringBuilder buf = new StringBuilder();
                    char[] chars = new char[2048];
                    for (; ; ) {
                        int len = reader.read(chars, 0, chars.length);
                        if (len < 0) {
                            break;
                        }
                        buf.append(chars, 0, len);
                    }

                    String text = buf.toString();
                    reader.close();

                    return (T) text;
                }
                return null;
            } catch (Exception e) {
                throw new RuntimeException("转换 clob 失败", e);
            }
        }
    }

    /**
     * BeanHelper.copyProperties(xxEntity,XXVO.class)
     * 建议采用BeanHelper.copyProperties(xxEntity,XXVO::new)  这种方式性能更好
     *
     * @param source
     * @param clazz
     * @param <T>
     * @param <S>
     * @return
     */
    public static <S, T> T copyProperties(S source, Class<T> clazz, String... ignoreProperties) {
        T o = null;
        if (source == null) {
            return null;
        } else if (source instanceof List) {
            return (T) copyListProperties((List) source, clazz, ignoreProperties);
        } else if (source instanceof Map) {
            return (T) copyProperties((Map) source, clazz, true, ignoreProperties);
        }
        try {
            o = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("无法实例化" + clazz + "类型");
        }
        return copyProperties(source, o, ignoreProperties);

    }

    /**
     * 推荐使用该方法
     * BeanHelper.copyProperties(xxEntity,XXVO::new)
     *
     * @param source
     * @param target
     * @param ignoreProperties
     * @param <T>
     * @return
     */
    public static <T, S> T copyProperties(S source, Supplier<T> target, String... ignoreProperties) {
        if (source == null) {
            return null;
        } else if (source instanceof List) {
            return (T) copyListProperties((List) source, target, ignoreProperties);
        } else if (source instanceof Map) {
            return (T) copyProperties((Map) source, target, ignoreProperties);
        } else {
            return copyProperties(source, target.get(), ignoreProperties);
        }
    }

    public static <S, T> T copyProperties(S source, T target, String... ignoreProperties) {
        if (source == null) {
            return null;
        }
        Class<?> actualEditable = target.getClass();

        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
//                    if (Collection.class.isAssignableFrom(targetPd.getPropertyType()) && Collection.class.isAssignableFrom(sourcePd.getPropertyType())) {
                    //    Collection children = (Collection) sourcePd.getReadMethod().invoke(source);
//                        for (Object val : children) {
//                            copyProperties( source, )
//                        }
//                        continue;
                    //                       System.out.println("不支持属性为List的类型转换");
                    //                   }
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && (ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) || rules.containsKey(readMethod.getReturnType()))) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            Converter converter = getConverter(sourcePd.getPropertyType());
                            if (converter != null) {
                                value = converter.convert(targetPd.getPropertyType(), value);
                            }

                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
        return target;
    }


    /**
     * 这个方法效率极低，不要使用 ，建议使用 BeanHelper.copyListProperties
     *
     * @param obj
     * @param clazz
     * @param <T>
     * @param <Y>
     * @return
     */
    @Deprecated()
    public static <T, Y> List<T> convertList(List<Y> obj, Class<T> clazz) {
        if (obj == null) {
            return null;
        }

        List<T> resultList = JSON.parseArray(JSON.toJSONString(obj), clazz);
        return resultList;
    }

    public static <T> Page<T> toPage(IPage<?> pageList, Class<T> clazz) {
        if (pageList == null) {
            return null;
        }

        Page<T> destPage = new Page<>();
        destPage.setPages(pageList.getPages());
        destPage.setSize(pageList.getSize());
        destPage.setCurrent(pageList.getCurrent());
        destPage.setTotal(pageList.getTotal());
        List<T> destList = copyListProperties(pageList.getRecords(), clazz);
        destPage.setRecords(destList);
        return destPage;
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Class<T> cls, String... ignoreProperties) {
        return copyListProperties(sources, cls, null, ignoreProperties);
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Class<T> cls, ColaBeanUtilsCallBack<S, T> callBack, String... ignoreProperties) {
        if (sources == null) {
            return null;
        }
        List<T> list = new ArrayList<>(sources.size());
        if (!CollectionUtils.isEmpty(sources) && sources.get(0) instanceof Map) {
            for (S m : sources) {
                try {
                    T t = copyProperties((Map<String, Object>) m, cls, true, ignoreProperties);

                    if (callBack != null) {
                        // 回调
                        callBack.callBack(m, t);
                    }
                    list.add(t);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return list;
        }
        for (S source : sources) {
            T t = null;
            try {
                t = cls.newInstance();
                copyProperties(source, t, ignoreProperties);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(source, t);
                }
                list.add(t);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> supplier, String... ignoreProperties) {

        return copyListProperties(sources, supplier, null, ignoreProperties);
    }

    /**
     * @author Johnson
     * 使用场景：Entity、Bo、Vo层数据的复制，因为BeanUtils.copyProperties只能给目标对象的属性赋值，却不能在List集合下循环赋值，因此添加该方法
     * 如：List<AdminEntity> 赋值到 List<AdminVo> ，List<AdminVo>中的 AdminVo 属性都会被赋予到值
     * S: 数据源类 ，T: 目标类::new(eg: AdminVo::new)
     */
    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> supplier, ColaBeanUtilsCallBack<S, T> callBack, String... ignoreProperties) {
        if (sources == null) {
            return null;
        }

        List<T> list = new ArrayList<>(sources.size());
        if (!CollectionUtils.isEmpty(sources) && sources.get(0) instanceof Map) {

            for (S m : sources) {
                T t = copyProperties((Map<String, Object>) m, supplier, true);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(m, t);
                }
                list.add(t);
            }
            return list;
        } else {
            for (S source : sources) {
                T t = copyProperties(source, supplier, ignoreProperties);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(source, t);
                }
                list.add(t);
            }
            return list;
        }
    }

    public static <M> M copyProperties(Map<String, Object> value, Supplier<M> supplier, String... ignoreField) {
        return copyProperties(value, supplier, true, ignoreField);
    }

    /**
     * @param value
     * @param supplier
     * @param cemalCase 是否下划线转cemalCase
     * @param <M>
     * @return
     */
    public static <M> M copyProperties(Map<String, Object> value, Supplier<M> supplier, boolean cemalCase, String... ignoreField) {
        M t = supplier.get();
        return copyProperties(value, supplier.get(), cemalCase);
    }

    public static <M> M copyProperties(Map<String, Object> value, Class<M> clz, String... ignoreField) {

        return copyProperties(value, clz, true, ignoreField);
    }

    public static <M> M copyProperties(Map<String, Object> value, Class<M> clz, boolean cemalCase, String... ignoreField) {
        M t = null;
        try {
            t = clz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new FatalBeanException("对象创建失败", e);
        }
        return copyProperties(value, t, cemalCase);
    }

    public static <M> M copyProperties(Map<String, Object> value, M target, boolean cemalCase, String... ignoreField) {
        M t = target;
        if (value == null) {
            return null;
        }
        List<String> ignoreList = Arrays.asList(ignoreField);
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(t.getClass());

        Map copy = new HashMap(value.size());
        if (cemalCase) {
            value.forEach((key, val) -> {
                if (ignoreList.contains(key)) {
                } else {
                    key = key.replaceAll("_", "").replaceAll("-", "").toUpperCase();
                    copy.put(key, val);
                }
            });

        } else {
            copy.putAll(value);
        }
        for (PropertyDescriptor targetPd : targetPds) {
            if (targetPd != null) {
                try {
                    Object o = copy.get(targetPd.getName().toUpperCase(Locale.ROOT));
                    if (null == o) {
                        continue;
                    }
                    Converter converter = getConverter(o.getClass());
                    if (converter != null) {
                        o = converter.convert(targetPd.getPropertyType(), o);
                    }
                    Method writeMethod = targetPd.getWriteMethod();
                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                        writeMethod.setAccessible(true);
                    }
                    writeMethod.invoke(t, ConverterSupport.cast(o, targetPd.getPropertyType()));
                } catch (Exception e) {
                    throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", e);
                }
            }
        }
        return t;
    }


    @FunctionalInterface
    public interface ColaBeanUtilsCallBack<S, T> {

        void callBack(S t, T s);
    }
}
